/*-------------------------------------------------------------------------
 *
 * nodeSubplan.c
 *	  支持出现在表达式中的子选择的例程
 *
 * 本模块负责执行SubPlan表达式节点，这与在FROM中出现的子SELECT不应混淆。SubPlans分为"initplans"，即每个查询只需评估一次（在其他限制下，这要求它们不使用任何来自父计划级别的直接相关变量），和"regular"子计划，每次需要其结果时都要重新评估。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/executor/nodeSubplan.c
 *
 *-------------------------------------------------------------------------
 */
/*
 *	 接口例程
 *		ExecSubPlan  - 处理一个子选择
 *		ExecInitSubPlan - 初始化一个子选择
 */
#include "postgres.h"

#include <math.h>

#include "access/htup_details.h"
#include "executor/executor.h"
#include "executor/nodeSubplan.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
#include "utils/array.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"

static Datum fc_ExecHashSubPlan(SubPlanState *fc_node,
							 ExprContext *fc_econtext,
							 bool *fc_isNull);
static Datum fc_ExecScanSubPlan(SubPlanState *fc_node,
							 ExprContext *fc_econtext,
							 bool *fc_isNull);
static void fc_buildSubPlanHash(SubPlanState *fc_node, ExprContext *fc_econtext);
static bool fc_findPartialMatch(TupleHashTable hashtable, TupleTableSlot *fc_slot,
							 FmgrInfo *fc_eqfunctions);
static bool fc_slotAllNulls(TupleTableSlot *fc_slot);
static bool fc_slotNoNulls(TupleTableSlot *fc_slot);


/* ----------------------------------------------------------------
 *		ExecSubPlan
 *
 * 这是执行常规子计划的主要入口点。
 * ----------------------------------------------------------------
 */
Datum ExecSubPlan(SubPlanState *fc_node,
			ExprContext *fc_econtext,
			bool *fc_isNull)
{
	SubPlan    *fc_subplan = fc_node->subplan;
	EState	   *fc_estate = fc_node->planstate->state;
	ScanDirection fc_dir = fc_estate->es_direction;
	Datum		fc_retval;

	CHECK_FOR_INTERRUPTS();

	/* 将非空设为默认值 */
	*fc_isNull = false;

	/* 合理性检查 */
	if (fc_subplan->subLinkType == CTE_SUBLINK)
		elog(ERROR, "CTE subplans should not be executed via ExecSubPlan");
	if (fc_subplan->setParam != NIL && fc_subplan->subLinkType != MULTIEXPR_SUBLINK)
		elog(ERROR, "cannot set parent params from subquery");

	/* 强制前向扫描模式以进行评估 */
	fc_estate->es_direction = ForwardScanDirection;

	/* 选择合适的评估策略 */
	if (fc_subplan->useHashTable)
		fc_retval = fc_ExecHashSubPlan(fc_node, fc_econtext, fc_isNull);
	else
		fc_retval = fc_ExecScanSubPlan(fc_node, fc_econtext, fc_isNull);

	/* 恢复扫描方向 */
	fc_estate->es_direction = fc_dir;

	return fc_retval;
}

/*
 * ExecHashSubPlan：将子选择结果存储在内存中的哈希表中
 */
static Datum fc_ExecHashSubPlan(SubPlanState *fc_node,
				ExprContext *fc_econtext,
				bool *fc_isNull)
{
	SubPlan    *fc_subplan = fc_node->subplan;
	PlanState  *fc_planstate = fc_node->planstate;
	TupleTableSlot *fc_slot;

	/* 不应该有任何直接相关的变量 */
	if (fc_subplan->parParam != NIL || fc_node->args != NIL)
		elog(ERROR, "hashed subplan with direct correlation not supported");

	/*
	 * 如果是第一次通过或需要重新扫描子计划，则构建哈希表。
	 */
	if (fc_node->hashtable == NULL || fc_planstate->chgParam != NULL)
		fc_buildSubPlanHash(fc_node, fc_econtext);

	/*
	 * 空子计划的结果总是 FALSE；无需评估左侧表达式。
	 */
	*fc_isNull = false;
	if (!fc_node->havehashrows && !fc_node->havenullrows)
		return BoolGetDatum(false);

	/*
	 * 评估左侧表达式并形成投影元组。首先，我们必须设置要使用的
	 * econtext（黑客警告！）。
	 */
	fc_node->projLeft->pi_exprContext = fc_econtext;
	fc_slot = ExecProject(fc_node->projLeft);

	/*
	 * 注意：因为我们通常在每个元组上下文中被调用，所以我们必须在
	 * 返回之前显式清除投影元组。否则，我们将出现双重释放情况：每个
	 * 元组上下文可能会在我们再次被调用之前被重置，然后元组插槽会
	 * 认为它仍然需要释放元组。
	 */

	/*
	 * 如果左侧全为非空，则在主哈希表中探测精确匹配。如果找到，结果
	 * 为 TRUE。否则，扫描部分空表以查看是否有任何行不明显与左侧不
	 * 相等；如果是，结果为 UNKNOWN。（如果我们不关心 UNKNOWN，
	 * 则跳过该部分。）否则，结果为 FALSE。
	 *
	 * 注意：我们可以避免完全扫描主哈希表的原因是，组合运算符假定
	 * 在两个输入都非空时永远不会产生 NULL。如果它们确实这样做，
	 * 我们可能需要产生 UNKNOWN 而不是 FALSE，因为在将左侧与主表
	 * 条目进行比较时可能存在 UNKNOWN 结果——这是一种我们甚至不
	 * 会进行的比较，除非有机会匹配哈希键。
	 */
	if (fc_slotNoNulls(fc_slot))
	{
		if (fc_node->havehashrows &&
			FindTupleHashEntry(fc_node->hashtable,
							   fc_slot,
							   fc_node->cur_eq_comp,
							   fc_node->lhs_hash_funcs) != NULL)
		{
			ExecClearTuple(fc_slot);
			return BoolGetDatum(true);
		}
		if (fc_node->havenullrows &&
			fc_findPartialMatch(fc_node->hashnulls, fc_slot, fc_node->cur_eq_funcs))
		{
			ExecClearTuple(fc_slot);
			*fc_isNull = true;
			return BoolGetDatum(false);
		}
		ExecClearTuple(fc_slot);
		return BoolGetDatum(false);
	}

	/*
	 * 当左侧部分或完全为空时，我们永远不能返回 TRUE。如果我们不关心
	 * UNKNOWN，则直接返回 FALSE。否则，如果左侧完全为空，则立即
	 * 返回 UNKNOWN。（由于组合运算符是严格的，结果仅在子选择为空时
	 * 为 FALSE，但我们已经处理过该情况。）否则，我们必须扫描主表
	 * 和部分空表，以查看是否有任何行不明显与左侧不相等；如果是，结果
	 * 为 UNKNOWN。否则，结果为 FALSE。
	 */
	if (fc_node->hashnulls == NULL)
	{
		ExecClearTuple(fc_slot);
		return BoolGetDatum(false);
	}
	if (fc_slotAllNulls(fc_slot))
	{
		ExecClearTuple(fc_slot);
		*fc_isNull = true;
		return BoolGetDatum(false);
	}
	/* 首先扫描部分空表，因为更可能获得匹配 */
	if (fc_node->havenullrows &&
		fc_findPartialMatch(fc_node->hashnulls, fc_slot, fc_node->cur_eq_funcs))
	{
		ExecClearTuple(fc_slot);
		*fc_isNull = true;
		return BoolGetDatum(false);
	}
	if (fc_node->havehashrows &&
		fc_findPartialMatch(fc_node->hashtable, fc_slot, fc_node->cur_eq_funcs))
	{
		ExecClearTuple(fc_slot);
		*fc_isNull = true;
		return BoolGetDatum(false);
	}
	ExecClearTuple(fc_slot);
	return BoolGetDatum(false);
}

/*
 * ExecScanSubPlan：默认情况，每次都必须重新扫描子计划
 */
static Datum fc_ExecScanSubPlan(SubPlanState *fc_node,
				ExprContext *fc_econtext,
				bool *fc_isNull)
{
	SubPlan    *fc_subplan = fc_node->subplan;
	PlanState  *fc_planstate = fc_node->planstate;
	SubLinkType fc_subLinkType = fc_subplan->subLinkType;
	MemoryContext fc_oldcontext;
	TupleTableSlot *fc_slot;
	Datum		fc_result;
	bool		fc_found = false;	/* 如果至少获得了一个子计划元组，则为真 */
	ListCell   *fc_pvar;
	ListCell   *fc_l;
	ArrayBuildStateAny *fc_astate = NULL;

	/* 如有需要，在调用者的上下文中初始化 ArrayBuildStateAny */
	if (fc_subLinkType == ARRAY_SUBLINK)
		fc_astate = initArrayResultAny(fc_subplan->firstColType,
									CurrentMemoryContext, true);

	/*
	 * 我们可能处于短暂的表达式评估上下文中。切换到每个查询上下文，
	 * 以便操作子计划的 chgParam，调用 ExecProcNode 等。
	 */
	fc_oldcontext = MemoryContextSwitchTo(fc_econtext->ecxt_per_query_memory);

	/*
	 * 从父计划的相关值中设置该计划的参数。（我们必须进行的任何
	 * 计算都是在父级 econtext 中完成的，因为参数值不需要具有每
	 * 个查询的生命周期。）
	 */
	Assert(list_length(fc_subplan->parParam) == list_length(fc_node->args));

	forboth(fc_l, fc_subplan->parParam, fc_pvar, fc_node->args)
	{
		int			fc_paramid = lfirst_int(fc_l);
		ParamExecData *fc_prm = &(fc_econtext->ecxt_param_exec_vals[fc_paramid]);

		fc_prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(fc_pvar),
											   fc_econtext,
											   &(fc_prm->isnull));
		fc_planstate->chgParam = bms_add_member(fc_planstate->chgParam, fc_paramid);
	}

	/*
	 * 现在我们已经设置了它的参数，我们可以重置子计划。
	 */
	ExecReScan(fc_planstate);

	
/*
	 * 对于所有的子链接类型，除了 EXPR_SUBLINK 和 ARRAY_SUBLINK，结果
	 * 是布尔值，组合运算符的结果也是如此。我们在元组之间组合结果（如果子计划产生多个元组）使用 OR
	 * 语义用于 ANY_SUBLINK 或 AND 语义用于 ALL_SUBLINK。
	 * （ROWCOMPARE_SUBLINK 不允许子计划返回多个元组。）
	 * 从组合运算符得到的 NULL 结果按照通常的 SQL 语义处理，OR 和 AND。对于没有输入元组的结果是
	 * ANY_SUBLINK 为 FALSE，ALL_SUBLINK 为 TRUE，ROWCOMPARE_SUBLINK 为 NULL。
	 *
	 * 对于 EXPR_SUBLINK，我们要求子计划最多产生一个
	 * 元组，否则会抛出错误。如果生成零个元组，则返回
	 * NULL。假设我们得到了一个元组，我们只使用它的第一列（在这种情况下只能有一个非垃圾列）。
	 *
	 * 对于 MULTIEXPR_SUBLINK，我们将每列的子计划输出推送到
	 * setParams 然后返回一个虚假的假值。子计划不能返回
	 * 多个元组；如果生成零个元组，则将 setParams 设置为 NULL。
	 *
	 * 对于 ARRAY_SUBLINK，我们允许子计划生成任意数量的元组，
	 * 并形成第一个列的值的数组。特别注意，如果没有生成元组，我们会生成一个零元素数组（这与
	 * 8.3 之前的行为不同，之前会返回 NULL）。
	 */
	fc_result = BoolGetDatum(fc_subLinkType == ALL_SUBLINK);
	*fc_isNull = false;

	for (fc_slot = ExecProcNode(fc_planstate);
		 !TupIsNull(fc_slot);
		 fc_slot = ExecProcNode(fc_planstate))
	{
		TupleDesc	fc_tdesc = fc_slot->tts_tupleDescriptor;
		Datum		fc_rowresult;
		bool		fc_rownull;
		int			fc_col;
		ListCell   *fc_plst;

		if (fc_subLinkType == EXISTS_SUBLINK)
		{
			fc_found = true;
			fc_result = BoolGetDatum(true);
			break;
		}

		if (fc_subLinkType == EXPR_SUBLINK)
		{
			/* 不能允许 EXPR 子链接有多个输入元组 */
			if (fc_found)
				ereport(ERROR,
						(errcode(ERRCODE_CARDINALITY_VIOLATION),
						 errmsg("more than one row returned by a subquery used as an expression")));
			fc_found = true;

			/*
			 * 我们需要复制子计划的元组，以防结果是
			 * 通过引用传递类型 --- 我们的返回值将指向这个
			 * 复制的元组！不能使用子计划的元组实例，
			 * 因为在下一次 ExecProcNode() 调用后它将不再有效。
			 * node->curTuple 跟踪复制的元组以供最终
			 * 释放。
			 */
			if (fc_node->curTuple)
				heap_freetuple(fc_node->curTuple);
			fc_node->curTuple = ExecCopySlotHeapTuple(fc_slot);

			fc_result = heap_getattr(fc_node->curTuple, 1, fc_tdesc, fc_isNull);
			/* 继续扫描子计划以确保只有一个元组 */
			continue;
		}

		if (fc_subLinkType == MULTIEXPR_SUBLINK)
		{
			/* 不能允许 MULTIEXPR 子链接有多个输入元组 */
			if (fc_found)
				ereport(ERROR,
						(errcode(ERRCODE_CARDINALITY_VIOLATION),
						 errmsg("more than one row returned by a subquery used as an expression")));
			fc_found = true;

			/*
			 * 我们需要复制子计划的元组，以防任何结果是
			 * 通过引用传递类型 --- 我们的输出值将指向这个
			 * 复制的元组！不能使用子计划的元组实例，
			 * 因为在下一次 ExecProcNode() 调用后它将不再有效。
			 * node->curTuple 跟踪复制的元组以供最终
			 * 释放。
			 */
			if (fc_node->curTuple)
				heap_freetuple(fc_node->curTuple);
			fc_node->curTuple = ExecCopySlotHeapTuple(fc_slot);

			/*
			 * 现在从元组的列中设置所有的 setParam 参数
			 */
			fc_col = 1;
			foreach(fc_plst, fc_subplan->setParam)
			{
				int			fc_paramid = lfirst_int(fc_plst);
				ParamExecData *fc_prmdata;

				fc_prmdata = &(fc_econtext->ecxt_param_exec_vals[fc_paramid]);
				Assert(fc_prmdata->execPlan == NULL);
				fc_prmdata->value = heap_getattr(fc_node->curTuple, fc_col, fc_tdesc,
											  &(fc_prmdata->isnull));
				fc_col++;
			}

			/* 继续扫描子计划以确保只有一个元组 */
			continue;
		}

		if (fc_subLinkType == ARRAY_SUBLINK)
		{
			Datum		fc_dvalue;
			bool		fc_disnull;

			fc_found = true;
			/* 储存当前值 */
			Assert(fc_subplan->firstColType == TupleDescAttr(fc_tdesc, 0)->atttypid);
			fc_dvalue = slot_getattr(fc_slot, 1, &fc_disnull);
			fc_astate = accumArrayResultAny(fc_astate, fc_dvalue, fc_disnull,
										 fc_subplan->firstColType, fc_oldcontext);
			/* 保持扫描子计划以收集所有值 */
			continue;
		}

		/* 对于 ROWCOMPARE 子链接也不能允许多个输入元组 */
		if (fc_subLinkType == ROWCOMPARE_SUBLINK && fc_found)
			ereport(ERROR,
					(errcode(ERRCODE_CARDINALITY_VIOLATION),
					 errmsg("more than one row returned by a subquery used as an expression")));

		fc_found = true;

		/*
		 * 对于 ALL、ANY 和 ROWCOMPARE 子链接，加载表示子选择列的 Params，
		 * 然后评估结合表达式。
		 */
		fc_col = 1;
		foreach(fc_plst, fc_subplan->paramIds)
		{
			int			fc_paramid = lfirst_int(fc_plst);
			ParamExecData *fc_prmdata;

			fc_prmdata = &(fc_econtext->ecxt_param_exec_vals[fc_paramid]);
			Assert(fc_prmdata->execPlan == NULL);
			fc_prmdata->value = slot_getattr(fc_slot, fc_col, &(fc_prmdata->isnull));
			fc_col++;
		}

		fc_rowresult = ExecEvalExprSwitchContext(fc_node->testexpr, fc_econtext,
											  &fc_rownull);

		if (fc_subLinkType == ANY_SUBLINK)
		{
			/* 按 OR 语义跨行组合 */
			if (fc_rownull)
				*fc_isNull = true;
			else if (DatumGetBool(fc_rowresult))
			{
				fc_result = BoolGetDatum(true);
				*fc_isNull = false;
				break;			/* 不需要查看更多行 */
			}
		}
		else if (fc_subLinkType == ALL_SUBLINK)
		{
			/* 按 AND 语义跨行组合 */
			if (fc_rownull)
				*fc_isNull = true;
			else if (!DatumGetBool(fc_rowresult))
			{
				fc_result = BoolGetDatum(false);
				*fc_isNull = false;
				break;			/* 不需要查看更多行 */
			}
		}
		else
		{
			/* 必须是 ROWCOMPARE_SUBLINK */
			fc_result = fc_rowresult;
			*fc_isNull = fc_rownull;
		}
	}

	MemoryContextSwitchTo(fc_oldcontext);

	if (fc_subLinkType == ARRAY_SUBLINK)
	{
		/* 我们在调用者的上下文中返回结果 */
		fc_result = makeArrayResultAny(fc_astate, fc_oldcontext, true);
	}
	else if (!fc_found)
	{
		/*
		 * 处理空子计划结果。result/isNull 之前已为所有子链接类型（除了 EXPR 和
		 * ROWCOMPARE）正确初始化；对于这些，返回 NULL。
		 */
		if (fc_subLinkType == EXPR_SUBLINK ||
			fc_subLinkType == ROWCOMPARE_SUBLINK)
		{
			fc_result = (Datum) 0;
			*fc_isNull = true;
		}
		else if (fc_subLinkType == MULTIEXPR_SUBLINK)
		{
			/* 我们不关心函数结果，但设置 setParams */
			foreach(fc_l, fc_subplan->setParam)
			{
				int			fc_paramid = lfirst_int(fc_l);
				ParamExecData *fc_prmdata;

				fc_prmdata = &(fc_econtext->ecxt_param_exec_vals[fc_paramid]);
				Assert(fc_prmdata->execPlan == NULL);
				fc_prmdata->value = (Datum) 0;
				fc_prmdata->isnull = true;
			}
		}
	}

	return fc_result;
}

/*
 * buildSubPlanHash: 通过扫描子计划输出加载哈希表。
 */
static void fc_buildSubPlanHash(SubPlanState *fc_node, ExprContext *fc_econtext)
{
	SubPlan    *fc_subplan = fc_node->subplan;
	PlanState  *fc_planstate = fc_node->planstate;
	int			fc_ncols = fc_node->numCols;
	ExprContext *fc_innerecontext = fc_node->innerecontext;
	MemoryContext fc_oldcontext;
	long		fc_nbuckets;
	TupleTableSlot *fc_slot;

	Assert(fc_subplan->subLinkType == ANY_SUBLINK);

	/*
	 * 如果我们已经有任何哈希表，则重置它们；否则创建空哈希表。
	 *
	 * 如果我们需要准确区分 IN 操作的 FALSE 和 UNKNOWN（即 NULL）结果，
	 * 那么我们必须存储子计划输出行，这些行部分或完全是 NULL。
	 * 我们将这些行存储在一个单独的哈希表中，我们期望它会比主表小得多。
	 * （我们可以使用哈希来消除不唯一的部分 NULL 行。我们将它们分开以最小化
	 * 不可避免的全表搜索的成本；见 findPartialMatch。）
	 *
	 * 如果不需要区分 FALSE 和 UNKNOWN，那么我们不需要存储包含 NULL 的
	 * 子计划输出行。
	 */
	MemoryContextReset(fc_node->hashtablecxt);
	fc_node->havehashrows = false;
	fc_node->havenullrows = false;

	fc_nbuckets = clamp_cardinality_to_long(fc_planstate->plan->plan_rows);
	if (fc_nbuckets < 1)
		fc_nbuckets = 1;

	if (fc_node->hashtable)
		ResetTupleHashTable(fc_node->hashtable);
	else
		fc_node->hashtable = BuildTupleHashTableExt(fc_node->parent,
												 fc_node->descRight,
												 fc_ncols,
												 fc_node->keyColIdx,
												 fc_node->tab_eq_funcoids,
												 fc_node->tab_hash_funcs,
												 fc_node->tab_collations,
												 fc_nbuckets,
												 0,
												 fc_node->planstate->state->es_query_cxt,
												 fc_node->hashtablecxt,
												 fc_node->hashtempcxt,
												 false);

	if (!fc_subplan->unknownEqFalse)
	{
		if (fc_ncols == 1)
			fc_nbuckets = 1;		/* 只能有一个条目 */
		else
		{
			fc_nbuckets /= 16;
			if (fc_nbuckets < 1)
				fc_nbuckets = 1;
		}

		if (fc_node->hashnulls)
			ResetTupleHashTable(fc_node->hashnulls);
		else
			fc_node->hashnulls = BuildTupleHashTableExt(fc_node->parent,
													 fc_node->descRight,
													 fc_ncols,
													 fc_node->keyColIdx,
													 fc_node->tab_eq_funcoids,
													 fc_node->tab_hash_funcs,
													 fc_node->tab_collations,
													 fc_nbuckets,
													 0,
													 fc_node->planstate->state->es_query_cxt,
													 fc_node->hashtablecxt,
													 fc_node->hashtempcxt,
													 false);
	}
	else
		fc_node->hashnulls = NULL;

	/*
	 * 我们可能处于短期表达式评估上下文中。切换到每个查询上下文以
	 * 操作子计划。
	 */
	fc_oldcontext = MemoryContextSwitchTo(fc_econtext->ecxt_per_query_memory);

	/*
	 * 重置子计划以重新开始。
	 */
	ExecReScan(fc_planstate);

	/*
	 * 扫描子计划并加载哈希表。注意，当有重复行从子选择中输出时，
	 * 仅存储一份副本。
	 */
	for (fc_slot = ExecProcNode(fc_planstate);
		 !TupIsNull(fc_slot);
		 fc_slot = ExecProcNode(fc_planstate))
	{
		int			fc_col = 1;
		ListCell   *fc_plst;
		bool		fc_isnew;

		/*
		 * 加载表示原始子选择输出的 Params，然后形成要存储在
		 * 哈希表中的投影元组。
		 */
		foreach(fc_plst, fc_subplan->paramIds)
		{
			int			fc_paramid = lfirst_int(fc_plst);
			ParamExecData *fc_prmdata;

			fc_prmdata = &(fc_innerecontext->ecxt_param_exec_vals[fc_paramid]);
			Assert(fc_prmdata->execPlan == NULL);
			fc_prmdata->value = slot_getattr(fc_slot, fc_col,
										  &(fc_prmdata->isnull));
			fc_col++;
		}
		fc_slot = ExecProject(fc_node->projRight);

		/*
		 * 如果结果包含任何 NULL，则单独存储或根本不存储。
		 */
		if (fc_slotNoNulls(fc_slot))
		{
			(void) LookupTupleHashEntry(fc_node->hashtable, fc_slot, &fc_isnew, NULL);
			fc_node->havehashrows = true;
		}
		else if (fc_node->hashnulls)
		{
			(void) LookupTupleHashEntry(fc_node->hashnulls, fc_slot, &fc_isnew, NULL);
			fc_node->havenullrows = true;
		}

		/*
		 * 在每个内部元组后重置内存上下文，以释放 ExecProject 期间使用的任何内存。
		 */
		ResetExprContext(fc_innerecontext);
	}

	/*
	 * 由于投影元组在子查询的上下文中而不是主上下文中，我们最好在任何
	 * 重置子查询上下文的机会出现之前清除元组槽。否则我们可能会有
	 * 尝试双重释放的潜在可能性。 （XXX 可能不再需要，但也无妨。）
	 */
	ExecClearTuple(fc_node->projRight->pi_state.resultslot);

	MemoryContextSwitchTo(fc_oldcontext);
}

/*
 * execTuplesUnequal
 *		如果两个元组在指示的字段中肯定不相等，则返回 true。
 *
 * NULL 既不等于也不与任何其他事物不等。仅当有非 NULL 字段不相等时，
 * 才能获得真实结果。
 *
 * slot1, slot2: 要比较的元组（必须具有相同的列！）
 * numCols: 要检查的属性数量
 * matchColIdx: 属性列号的数组
 * eqFunctions: 用于使用的相等函数的 fmgr 查找信息数组
 * evalContext: 执行函数的短期内存上下文
 */
static bool fc_execTuplesUnequal(TupleTableSlot *fc_slot1,
				  TupleTableSlot *fc_slot2,
				  int fc_numCols,
				  AttrNumber *fc_matchColIdx,
				  FmgrInfo *fc_eqfunctions,
				  const Oid *fc_collations,
				  MemoryContext fc_evalContext)
{
	MemoryContext fc_oldContext;
	bool		fc_result;
	int			fc_i;

	/* 重置并切换到临时上下文。 */
	MemoryContextReset(fc_evalContext);
	fc_oldContext = MemoryContextSwitchTo(fc_evalContext);

	/*
	 * 我们不能在不检查所有字段的情况下报告匹配，但是一旦发现不相等字段，
	 * 我们可以立即报告不匹配。因此，从最后一个字段（最不重要的排序键）
	 * 开始比较。如果我们处理的是排序输入，那么这最有可能不同。
	 */
	fc_result = false;

	for (fc_i = fc_numCols; --fc_i >= 0;)
	{
		AttrNumber	fc_att = fc_matchColIdx[fc_i];
		Datum		fc_attr1,
					fc_attr2;
		bool		fc_isNull1,
					fc_isNull2;

		fc_attr1 = slot_getattr(fc_slot1, fc_att, &fc_isNull1);

		if (fc_isNull1)
			continue;			/* 在这里不能证明任何事情 */

		fc_attr2 = slot_getattr(fc_slot2, fc_att, &fc_isNull2);

		if (fc_isNull2)
			continue;			/* 在这里不能证明任何事情 */

		/* 应用类型特定的相等函数 */
		if (!DatumGetBool(FunctionCall2Coll(&fc_eqfunctions[fc_i],
											fc_collations[fc_i],
											fc_attr1, fc_attr2)))
		{
			fc_result = true;		/* 它们不相等 */
			break;
		}
	}

	MemoryContextSwitchTo(fc_oldContext);

	return fc_result;
}

/*
 * findPartialMatch: 哈希表中是否包含一个条目，该条目与元组
 * 不可证明是不同的？
 *
 * 我们必须扫描整个哈希表；我们不能有效地使用哈希键来指导探测，
 * 因为我们可能会在与给定元组的哈希键完全无关的元组上得到部分匹配。
 *
 * 调用者必须提供要使用的相等函数，因为在跨类型情况下，这些函数与
 * 哈希表的内部函数不同。
 */
static bool fc_findPartialMatch(TupleHashTable hashtable, TupleTableSlot *fc_slot,
				 FmgrInfo *fc_eqfunctions)
{
	int			fc_numCols = hashtable->numCols;
	AttrNumber *fc_keyColIdx = hashtable->keyColIdx;
	TupleHashIterator fc_hashiter;
	TupleHashEntry fc_entry;

	InitTupleHashIterator(hashtable, &fc_hashiter);
	while ((fc_entry = ScanTupleHashTable(hashtable, &fc_hashiter)) != NULL)
	{
		CHECK_FOR_INTERRUPTS();

		ExecStoreMinimalTuple(fc_entry->firstTuple, hashtable->tableslot, false);
		if (!fc_execTuplesUnequal(fc_slot, hashtable->tableslot,
							   fc_numCols, fc_keyColIdx,
							   fc_eqfunctions,
							   hashtable->tab_collations,
							   hashtable->tempcxt))
		{
			TermTupleHashIterator(&hashiter);
			return true;
		}
	}
	/* 这里不需要 TermTupleHashIterator 调用 */
	return false;
}

/*
 * slotAllNulls: 插槽是否完全为 NULL？
 *
 * 这并不测试已删除的列，这是可以的，因为我们只在投影元组上使用它。
 */
static bool fc_slotAllNulls(TupleTableSlot *fc_slot)
{
	int			fc_ncols = fc_slot->tts_tupleDescriptor->natts;
	int			fc_i;

	for (fc_i = 1; fc_i <= fc_ncols; fc_i++)
	{
		if (!slot_attisnull(fc_slot, fc_i))
			return false;
	}
	return true;
}

/*
 * slotNoNulls: 该槽位是否完全不为 NULL？
 *
 * 这不会测试已删除的列，这很好，因为我们只在投影元组上使用它。
 */
static bool fc_slotNoNulls(TupleTableSlot *fc_slot)
{
	int			fc_ncols = fc_slot->tts_tupleDescriptor->natts;
	int			fc_i;

	for (fc_i = 1; fc_i <= fc_ncols; fc_i++)
	{
		if (slot_attisnull(fc_slot, fc_i))
			return false;
	}
	return true;
}

/* ----------------------------------------------------------------
 *		ExecInitSubPlan
 *
 * 为 SubPlan 创建一个 SubPlanState；这是 ExecInitExpr() 的 SubPlan 特定部分。
 * 我们将其拆分出来，以便它可以用于 InitPlans 以及常规 SubPlans。
 * 请注意，我们不会将 SubPlan 链接到父级的 subPlan 列表中，因为这不应该发生在 InitPlans 中。
 * 相反，ExecInitExpr() 仅执行这一部分。
 * ----------------------------------------------------------------
 */
SubPlanState *
ExecInitSubPlan(SubPlan *fc_subplan, PlanState *fc_parent)
{
	SubPlanState *fc_sstate = makeNode(SubPlanState);
	EState	   *fc_estate = fc_parent->state;

	fc_sstate->subplan = fc_subplan;

	/* 将 SubPlanState 链接到已经初始化的子计划 */
	fc_sstate->planstate = (PlanState *) list_nth(fc_estate->es_subplanstates,
											   fc_subplan->plan_id - 1);

	/*
	 * 如果计划者错误地将一个并行不安全的子计划放入并行化的子查询中，
	 * 则此检查可能会失败；参见 ExecSerializePlan。
	 */
	if (fc_sstate->planstate == NULL)
		elog(ERROR, "subplan \"%s\" was not initialized",
			 fc_subplan->plan_name);

	/* 也链接到父级的状态 */
	fc_sstate->parent = fc_parent;

	/* 初始化子表达式 */
	fc_sstate->testexpr = ExecInitExpr((Expr *) fc_subplan->testexpr, fc_parent);
	fc_sstate->args = ExecInitExprList(fc_subplan->args, fc_parent);

	/*
	 * 初始化我的状态
	 */
	fc_sstate->curTuple = NULL;
	fc_sstate->curArray = PointerGetDatum(NULL);
	fc_sstate->projLeft = NULL;
	fc_sstate->projRight = NULL;
	fc_sstate->hashtable = NULL;
	fc_sstate->hashnulls = NULL;
	fc_sstate->hashtablecxt = NULL;
	fc_sstate->hashtempcxt = NULL;
	fc_sstate->innerecontext = NULL;
	fc_sstate->keyColIdx = NULL;
	fc_sstate->tab_eq_funcoids = NULL;
	fc_sstate->tab_hash_funcs = NULL;
	fc_sstate->tab_eq_funcs = NULL;
	fc_sstate->tab_collations = NULL;
	fc_sstate->lhs_hash_funcs = NULL;
	fc_sstate->cur_eq_funcs = NULL;

	/*
	 * 如果这是一个 initplan，它具有父计划将使用的输出参数，
	 * 因此将这些参数标记为需要评估。我们实际上不会在首先需要
	 * 其输出之前运行子计划。
	 *
	 * CTE 子计划的输出参数在正常情况下永远不会被评估，
	 * 所以在这种情况下跳过此步骤。
	 *
	 * 请注意，我们在这里不设置 parent->chgParam：父计划尚未运行，
	 * 所以没有必要强制它重新运行。
	 */
	if (fc_subplan->setParam != NIL && fc_subplan->parParam == NIL &&
		fc_subplan->subLinkType != CTE_SUBLINK)
	{
		ListCell   *fc_lst;

		foreach(fc_lst, fc_subplan->setParam)
		{
			int			fc_paramid = lfirst_int(fc_lst);
			ParamExecData *fc_prm = &(fc_estate->es_param_exec_vals[fc_paramid]);

			fc_prm->execPlan = fc_sstate;
		}
	}

	/*
	 * 如果我们要对子查询输出进行哈希，初始化相关内容。
	 * （不过我们不会在需要之前创建哈希表。）
	 */
	if (fc_subplan->useHashTable)
	{
		int			fc_ncols,
					fc_i;
		TupleDesc	fc_tupDescLeft;
		TupleDesc	fc_tupDescRight;
		Oid		   *fc_cross_eq_funcoids;
		TupleTableSlot *fc_slot;
		List	   *fc_oplist,
				   *fc_lefttlist,
				   *fc_righttlist;
		ListCell   *fc_l;

		/* 我们需要一个内存上下文来保存哈希表 */
		fc_sstate->hashtablecxt =
			AllocSetContextCreate(CurrentMemoryContext,
								  "Subplan HashTable Context",
								  ALLOCSET_DEFAULT_SIZES);
		/* 和一个小的用于哈希表作为临时存储 */
		fc_sstate->hashtempcxt =
			AllocSetContextCreate(CurrentMemoryContext,
								  "Subplan HashTable Temp Context",
								  ALLOCSET_SMALL_SIZES);
		/* 以及一个短期的 exprcontext 用于函数评估 */
		fc_sstate->innerecontext = CreateExprContext(fc_estate);

		/*
		 * 我们使用 ExecProject 来评估左侧和右侧的表达式列表并形成元组。
		 * （你可能会认为我们可以直接使用子选择的输出元组，
		 * 但如果我们不得不插入任何运行时强制转换子选择输出数据类型，这就不行；
		 * 无论如何，这避免了存储子选择输出中可能存在的 resjunk 列。）
		 * 通过结合表达式构建左侧和右侧的 tlists。
		 *
		 * 我们还提取了组合运算符本身，以初始化哈希表的
		 * 等式和哈希函数。
		 */
		if (IsA(fc_subplan->testexpr, OpExpr))
		{
			/* 单个组合运算符 */
			fc_oplist = list_make1(fc_subplan->testexpr);
		}
		else if (is_andclause(fc_subplan->testexpr))
		{
			/* 多个组合运算符 */
			fc_oplist = castNode(BoolExpr, fc_subplan->testexpr)->args;
		}
		else
		{
			/* 在可哈希子计划中不应看到其他内容 */
			elog(ERROR, "unrecognized testexpr type: %d",
				 (int) nodeTag(fc_subplan->testexpr));
			fc_oplist = NIL;		/* 保持编译器安静 */
		}
		fc_ncols = list_length(fc_oplist);

		fc_lefttlist = fc_righttlist = NIL;
		fc_sstate->numCols = fc_ncols;
		fc_sstate->keyColIdx = (AttrNumber *) palloc(fc_ncols * sizeof(AttrNumber));
		fc_sstate->tab_eq_funcoids = (Oid *) palloc(fc_ncols * sizeof(Oid));
		fc_sstate->tab_collations = (Oid *) palloc(fc_ncols * sizeof(Oid));
		fc_sstate->tab_hash_funcs = (FmgrInfo *) palloc(fc_ncols * sizeof(FmgrInfo));
		fc_sstate->tab_eq_funcs = (FmgrInfo *) palloc(fc_ncols * sizeof(FmgrInfo));
		fc_sstate->lhs_hash_funcs = (FmgrInfo *) palloc(fc_ncols * sizeof(FmgrInfo));
		fc_sstate->cur_eq_funcs = (FmgrInfo *) palloc(fc_ncols * sizeof(FmgrInfo));
		/* 我们会在下面需要跨类型的相等函数，但不在 sstate 中 */
		fc_cross_eq_funcoids = (Oid *) palloc(fc_ncols * sizeof(Oid));

		fc_i = 1;
		foreach(fc_l, fc_oplist)
		{
			OpExpr	   *fc_opexpr = lfirst_node(OpExpr, fc_l);
			Expr	   *fc_expr;
			TargetEntry *fc_tle;
			Oid			fc_rhs_eq_oper;
			Oid			fc_left_hashfn;
			Oid			fc_right_hashfn;

			Assert(list_length(fc_opexpr->args) == 2);

			/* 处理左侧参数 */
			fc_expr = (Expr *) linitial(fc_opexpr->args);
			fc_tle = makeTargetEntry(fc_expr,
								  fc_i,
								  NULL,
								  false);
			fc_lefttlist = lappend(fc_lefttlist, fc_tle);

			/* 处理右侧参数 */
			fc_expr = (Expr *) lsecond(fc_opexpr->args);
			fc_tle = makeTargetEntry(fc_expr,
								  fc_i,
								  NULL,
								  false);
			fc_righttlist = lappend(fc_righttlist, fc_tle);

			/* 查找相等函数（可能是跨类型） */
			fc_cross_eq_funcoids[fc_i - 1] = fc_opexpr->opfuncid;
			fmgr_info(fc_opexpr->opfuncid, &fc_sstate->cur_eq_funcs[fc_i - 1]);
			fmgr_info_set_expr((Node *) fc_opexpr, &fc_sstate->cur_eq_funcs[fc_i - 1]);

			/* 查找 RHS 类型的相等函数 */
			if (!get_compatible_hash_operators(fc_opexpr->opno,
											   NULL, &fc_rhs_eq_oper))
				elog(ERROR, "could not find compatible hash operator for operator %u",
					 fc_opexpr->opno);
			fc_sstate->tab_eq_funcoids[fc_i - 1] = get_opcode(fc_rhs_eq_oper);
			fmgr_info(fc_sstate->tab_eq_funcoids[fc_i - 1],
					  &fc_sstate->tab_eq_funcs[fc_i - 1]);

			/* 查找关联的哈希函数 */
			if (!get_op_hash_functions(fc_opexpr->opno,
									   &fc_left_hashfn, &fc_right_hashfn))
				elog(ERROR, "could not find hash function for hash operator %u",
					 fc_opexpr->opno);
			fmgr_info(fc_left_hashfn, &fc_sstate->lhs_hash_funcs[fc_i - 1]);
			fmgr_info(fc_right_hashfn, &fc_sstate->tab_hash_funcs[fc_i - 1]);

			/* 设置排序规则 */
			fc_sstate->tab_collations[fc_i - 1] = fc_opexpr->inputcollid;

			/* keyColIdx 只是列号 1..n */
			fc_sstate->keyColIdx[fc_i - 1] = fc_i;

			fc_i++;
		}

		/*
		 * 为左右两侧构造 tupdescs、槽和投影节点。
		 * 左侧表达式将在父计划节点的 exprcontext 中评估，
		 * 而我们在这里无法访问。幸运的是，我们现在可以传递 NULL
		 * 并在稍后填充它（黑客警告！）。右侧表达式将在我们自己的
		 * innerecontext 中评估。
		 */
		fc_tupDescLeft = ExecTypeFromTL(fc_lefttlist);
		fc_slot = ExecInitExtraTupleSlot(fc_estate, fc_tupDescLeft, &TTSOpsVirtual);
		fc_sstate->projLeft = ExecBuildProjectionInfo(fc_lefttlist,
												   NULL,
												   fc_slot,
												   fc_parent,
												   NULL);

		fc_sstate->descRight = fc_tupDescRight = ExecTypeFromTL(fc_righttlist);
		fc_slot = ExecInitExtraTupleSlot(fc_estate, fc_tupDescRight, &TTSOpsVirtual);
		fc_sstate->projRight = ExecBuildProjectionInfo(fc_righttlist,
													fc_sstate->innerecontext,
													fc_slot,
													fc_sstate->planstate,
													NULL);

		/*
		 * 创建用于查找表中行的比较器（可能是跨类型比较）。
		 */
		fc_sstate->cur_eq_comp = ExecBuildGroupingEqual(fc_tupDescLeft, fc_tupDescRight,
													 &TTSOpsVirtual, &TTSOpsMinimalTuple,
													 fc_ncols,
													 fc_sstate->keyColIdx,
													 fc_cross_eq_funcoids,
													 fc_sstate->tab_collations,
													 fc_parent);
	}

	return fc_sstate;
}


/* ----------------------------------------------------------------
 *		ExecSetParamPlan
 *
 *		执行子计划并设置其输出参数。
 *
 * 当请求 PARAM_EXEC 参数的值并且参数的 execPlan 字段已设置时，将从 ExecEvalParamExec() 调用该函数
 * （这表明该参数尚未被评估）。这允许延迟评估 initplans：我们只有在需要其输出时才运行子计划。
 * 请注意，评估完成后，此例程必须清除计划输出参数的 execPlan 字段！
 *
 * 此函数的结果存储在与 ExprContext 关联的 EState 中（特别是其 ecxt_param_exec_vals）；任何按引用传递的结果 
 * Datums 都在 EState 的每查询内存中分配。传递的 econtext 可以是属于该 EState 的任何 ExprContext；哪个 
 * 重要性只在于 ExprContext 的每元组内存上下文用于评估传递到子计划的任何参数。
 * （因此原则上，ExprContext 的生命周期越短越好，因为返回后不再需要该数据。在实践中，由于 initplan
 * 参数从不比 Vars，Aggrefs 等复杂，因此评估它们当前无论如何永远不会泄漏任何内存。）
 * ----------------------------------------------------------------
 */
void ExecSetParamPlan(SubPlanState *fc_node, ExprContext *fc_econtext)
{
	SubPlan    *fc_subplan = fc_node->subplan;
	PlanState  *fc_planstate = fc_node->planstate;
	SubLinkType fc_subLinkType = fc_subplan->subLinkType;
	EState	   *fc_estate = fc_planstate->state;
	ScanDirection fc_dir = fc_estate->es_direction;
	MemoryContext fc_oldcontext;
	TupleTableSlot *fc_slot;
	ListCell   *fc_l;
	bool		fc_found = false;
	ArrayBuildStateAny *fc_astate = NULL;

	if (fc_subLinkType == ANY_SUBLINK ||
		fc_subLinkType == ALL_SUBLINK)
		elog(ERROR, "ANY/ALL subselect unsupported as initplan");
	if (fc_subLinkType == CTE_SUBLINK)
		elog(ERROR, "CTE subplans should not be executed via ExecSetParamPlan");
	if (fc_subplan->parParam || fc_node->args)
		elog(ERROR, "correlated subplans should not be executed via ExecSetParamPlan");

	/*
	 * 强制前向扫描方向，无论调用者如何。虽然很困难，但在后向扫描中到达这里并非不可能，因此无论如何都要使其工作。
	 */
	fc_estate->es_direction = ForwardScanDirection;

	/* 如有需要，在调用者的上下文中初始化 ArrayBuildStateAny */
	if (fc_subLinkType == ARRAY_SUBLINK)
		fc_astate = initArrayResultAny(fc_subplan->firstColType,
									CurrentMemoryContext, true);

	/*
	 * 必须切换到每查询内存上下文。
	 */
	fc_oldcontext = MemoryContextSwitchTo(fc_econtext->ecxt_per_query_memory);

	/*
	 * 运行计划。（如果需要重新扫描，第一次 ExecProcNode 调用将处理此事。）
	 */
	for (fc_slot = ExecProcNode(fc_planstate);
		 !TupIsNull(fc_slot);
		 fc_slot = ExecProcNode(fc_planstate))
	{
		TupleDesc	fc_tdesc = fc_slot->tts_tupleDescriptor;
		int			fc_i = 1;

		if (fc_subLinkType == EXISTS_SUBLINK)
		{
			/* 只能有一个 setParam... */
			int			fc_paramid = linitial_int(fc_subplan->setParam);
			ParamExecData *fc_prm = &(fc_econtext->ecxt_param_exec_vals[fc_paramid]);

			fc_prm->execPlan = NULL;
			fc_prm->value = BoolGetDatum(true);
			fc_prm->isnull = false;
			fc_found = true;
			break;
		}

		if (fc_subLinkType == ARRAY_SUBLINK)
		{
			Datum		fc_dvalue;
			bool		fc_disnull;

			fc_found = true;
			/* 储存当前值 */
			Assert(fc_subplan->firstColType == TupleDescAttr(fc_tdesc, 0)->atttypid);
			fc_dvalue = slot_getattr(fc_slot, 1, &fc_disnull);
			fc_astate = accumArrayResultAny(fc_astate, fc_dvalue, fc_disnull,
										 fc_subplan->firstColType, fc_oldcontext);
			/* 保持扫描子计划以收集所有值 */
			continue;
		}

		if (fc_found &&
			(fc_subLinkType == EXPR_SUBLINK ||
			 fc_subLinkType == MULTIEXPR_SUBLINK ||
			 fc_subLinkType == ROWCOMPARE_SUBLINK))
			ereport(ERROR,
					(errcode(ERRCODE_CARDINALITY_VIOLATION),
					 errmsg("more than one row returned by a subquery used as an expression")));

		fc_found = true;

		/*
		 * 我们需要将子计划的元组复制到我们自己的上下文中，以防
		 * 任何参数是按引用传递的类型 --- 存储在
		 * 参数结构中的指针将指向这个复制的元组！ node->curTuple
		 * 用于跟踪已复制的元组以便最终释放。
		 */
		if (fc_node->curTuple)
			heap_freetuple(fc_node->curTuple);
		fc_node->curTuple = ExecCopySlotHeapTuple(fc_slot);

		/*
		 * 现在从元组的列中设置所有 setParam 参数
		 */
		foreach(fc_l, fc_subplan->setParam)
		{
			int			fc_paramid = lfirst_int(fc_l);
			ParamExecData *fc_prm = &(fc_econtext->ecxt_param_exec_vals[fc_paramid]);

			fc_prm->execPlan = NULL;
			fc_prm->value = heap_getattr(fc_node->curTuple, fc_i, fc_tdesc,
									  &(fc_prm->isnull));
			fc_i++;
		}
	}

	if (fc_subLinkType == ARRAY_SUBLINK)
	{
		/* 只能有一个 setParam... */
		int			fc_paramid = linitial_int(fc_subplan->setParam);
		ParamExecData *fc_prm = &(fc_econtext->ecxt_param_exec_vals[fc_paramid]);

		/*
		 * 我们在查询上下文中构建结果数组，以防它消失；
		 * 为了避免在重复调用之间泄漏内存，我们必须记住
		 * 最新的值，就像上面的 curTuple。
		 */
		if (fc_node->curArray != PointerGetDatum(NULL))
			pfree(DatumGetPointer(fc_node->curArray));
		fc_node->curArray = makeArrayResultAny(fc_astate,
											fc_econtext->ecxt_per_query_memory,
											true);
		fc_prm->execPlan = NULL;
		fc_prm->value = fc_node->curArray;
		fc_prm->isnull = false;
	}
	else if (!fc_found)
	{
		if (fc_subLinkType == EXISTS_SUBLINK)
		{
			/* 只能有一个 setParam... */
			int			fc_paramid = linitial_int(fc_subplan->setParam);
			ParamExecData *fc_prm = &(fc_econtext->ecxt_param_exec_vals[fc_paramid]);

			fc_prm->execPlan = NULL;
			fc_prm->value = BoolGetDatum(false);
			fc_prm->isnull = false;
		}
		else
		{
			/* 对于其他子链接类型，将所有输出参数设置为 NULL */
			foreach(fc_l, fc_subplan->setParam)
			{
				int			fc_paramid = lfirst_int(fc_l);
				ParamExecData *fc_prm = &(fc_econtext->ecxt_param_exec_vals[fc_paramid]);

				fc_prm->execPlan = NULL;
				fc_prm->value = (Datum) 0;
				fc_prm->isnull = true;
			}
		}
	}

	MemoryContextSwitchTo(fc_oldcontext);

	/* 恢复扫描方向 */
	fc_estate->es_direction = fc_dir;
}

/*
 * ExecSetParamPlanMulti
 *
 * 应用 ExecSetParamPlan 来评估“params”中列出的任何尚未评估的初始化计划输出
 * 参数。任何列出的参数如果不是初始化计划输出则会被忽略。
 *
 * 与 ExecSetParamPlan 一样，当前 EState 属于任何 ExprContext
 * 都可以使用，但原则上，短生命周期的 ExprContext 比长生命周期的更好。
 */
void ExecSetParamPlanMulti(const Bitmapset *fc_params, ExprContext *fc_econtext)
{
	int			fc_paramid;

	fc_paramid = -1;
	while ((fc_paramid = bms_next_member(fc_params, fc_paramid)) >= 0)
	{
		ParamExecData *fc_prm = &(fc_econtext->ecxt_param_exec_vals[fc_paramid]);

		if (fc_prm->execPlan != NULL)
		{
			/*参数尚未评估，所以去做它 */
			ExecSetParamPlan(fc_prm->execPlan, fc_econtext);
			/*ExecSetParamPlan应该已经处理了这个参数... */
			Assert(fc_prm->execPlan == NULL);
		}
	}
}

/*
 * 标记初始化计划为需要重新计算
 */
void ExecReScanSetParamPlan(SubPlanState *fc_node, PlanState *fc_parent)
{
	PlanState  *fc_planstate = fc_node->planstate;
	SubPlan    *fc_subplan = fc_node->subplan;
	EState	   *fc_estate = fc_parent->state;
	ListCell   *fc_l;

	/* 运行检查 */
	if (fc_subplan->parParam != NIL)
		elog(ERROR, "direct correlated subquery unsupported as initplan");
	if (fc_subplan->setParam == NIL)
		elog(ERROR, "setParam list of initplan is empty");
	if (bms_is_empty(fc_planstate->plan->extParam))
		elog(ERROR, "extParam set of initplan is empty");

	/*
	 * 不实际重新扫描：如果需要的话，它将在 ExecSetParamPlan 内部发生。
	 */

	/*
	 * 标记这个子计划的输出参数为需要重新计算。
	 *
	 * CTE 子计划从不通过参数重新计算执行；相反
	 * 它们在被 nodeCtescan.c 调用时执行。因此，不要将 CTE
	 * 子计划的输出参数标记为脏，但确实为其设置 chgParam 位
	 * 以便依赖的计划节点会被告知重新扫描。
	 */
	foreach(fc_l, fc_subplan->setParam)
	{
		int			fc_paramid = lfirst_int(fc_l);
		ParamExecData *fc_prm = &(fc_estate->es_param_exec_vals[fc_paramid]);

		if (fc_subplan->subLinkType != CTE_SUBLINK)
			fc_prm->execPlan = fc_node;

		fc_parent->chgParam = bms_add_member(fc_parent->chgParam, fc_paramid);
	}
}
